package com.android.stockme.backend.news;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

import android.util.Log;

import com.android.stockme.backend.configuration.IConfigurationChangedListener;
import com.android.stockme.backend.configuration.IConfigurationManager;
import com.android.stockme.backend.fetcher.INewsItem;

/**
 * 
 * @author Avishay
 *
 * This class is an implementation of a News Updates Center.
 * 
 * The News Updates Center is responsible for maintaining the news updates gathered by the system. It also enables extracting the news updates
 * sorted by date or by filter.
 * 
 */
public class NewsUpdatesCenterImpl implements INewsUpdatesCenter {

	private Object mutex = new Object();
	
	private List<INewsItem> items = null;
	
	private Collection<INewsUpdateReceivedListener> listeners = null;
	
	private IConfigurationManager configurationManager = null;

	public NewsUpdatesCenterImpl(IConfigurationManager configurationManager) {
		this.configurationManager = configurationManager;
		this.listeners = new HashSet<INewsUpdateReceivedListener>();
		this.items = new LinkedList<INewsItem>();
		
		// Listen to changes in the Stock configuration in order to add/remove stocks from the news database
		this.configurationManager.registerStockRemovedListener(new IConfigurationChangedListener() {
			public void notifyConfigurationChanged(Object arg) {
				removeStockRelatedNews((String)arg);
				postNewsUpdateReceivedEvent();
			}
		});
		
		// Listen to changes in the application configuration in order to increase/decrease the news history length 
		this.configurationManager.registerHistoryLengthChangedListener(new IConfigurationChangedListener() {
			public void notifyConfigurationChanged(Object arg) {
				evictLRU(0);
				postNewsUpdateReceivedEvent();
			}			
		});
	}
	
	public SortedSet<INewsItem> fetchNewsSortedByDate() {
		SortedSet<INewsItem> items_cloned = new TreeSet<INewsItem>();
		
		synchronized (this.mutex) {
			Iterator<INewsItem> i = this.items.iterator();
			while (i.hasNext()) {
				INewsItem newsItem = (INewsItem)i.next();
				try {
					items_cloned.add(newsItem.clone());
				} catch (CloneNotSupportedException e) {
					Log.e("StockMe", e.getMessage());
				}
			}
		}
		
		return items_cloned;
	}

	public SortedSet<INewsItem> fetchNewsSortedByFilter(String filter) {
		SortedSet<INewsItem> items_cloned = new TreeSet<INewsItem>();

		synchronized (this.mutex) {
			Iterator<INewsItem> i = this.items.iterator();
			while (i.hasNext()) {
				INewsItem newsItem = (INewsItem)i.next();
				try {
					if (newsItem.getSymbol().compareTo(filter) == 0)
						items_cloned.add(newsItem.clone());
				} catch (CloneNotSupportedException e) {
					Log.e("StockMe", e.getMessage());
				}
			}
		}
		
		return items_cloned;
	}

	public void registerNewsUpdateReceivedListener(INewsUpdateReceivedListener listener) {
		if (listener != null)
			this.listeners.add(listener);
	}

	public void unregisterNewsUpdateReceivedListener(INewsUpdateReceivedListener listener) {
		if (listener != null)
			this.listeners.remove(listener);
	}

	public void updateNewsCenter(List<INewsItem> dItems) {
		int num_items = this.configurationManager.getNewsHistoryLength(); 
				
		synchronized (this.mutex) {
		
			// Clear the current news set
			if (this.items !=  null)
				this.items.clear();
		
			if (dItems == null)
			{
				this.postNewsUpdateReceivedEvent();
				return;
			}
			
			// Create a linked list from the given items and sort it
			this.items = dItems;
			Collections.sort(this.items);
			while ((this.items.size() >  num_items) && (!this.items.isEmpty()))
				this.items.remove(0);
			Collections.reverse(this.items);

		}

		this.postNewsUpdateReceivedEvent();
	}

	protected void postNewsUpdateReceivedEvent() {
		for (INewsUpdateReceivedListener listener : this.listeners)
			listener.notifyNewsUpdateReceived(null);
	}

	private void removeStockRelatedNews(String symbol) {
		
		if ((symbol == null) || (this.items.size() == 0))
			return;
		
		synchronized(this.mutex) {
			int i = 0;
			while (i < this.items.size()) {

				if (this.items.get(i).getSymbol().compareTo(symbol) == 0)
					this.items.remove(i);
				else
					i++;
			}
		}
	}
	
	private void evictLRU(final int updatesNum) {
		final int DB_SIZE = this.configurationManager.getNewsHistoryLength();

		

		// Remove items from the DB until there is enough room for the new items or there are no more items
		synchronized (this.mutex) {
			int rowsCount = this.items.size();
			Collections.reverse(this.items);
			while ((DB_SIZE < (rowsCount + updatesNum)) && !this.items.isEmpty()) {
	
				// Remove the first item (LRU) in the Set until we reach the needed space
				this.items.remove(0);
				
				rowsCount--;
			}
			Collections.reverse(this.items);
		}
	}
}
